'''Simple request-response TCP server.

Shared between master monitoring server, and individual monitord nodes.
Nodes only respond to pings - master responds to other commands too.
'''
import struct
import threading
import socket
import net

BUFFSIZE=4096 #recv buffer


def connection_handler(sock, addr, request_handler):
    '''Individual connection handler.

Request-response protocol definition:
    Request packet structure: [WORD length of packet][BYTE type id][VOID data]
    Response packet structure: [WORD length of packet][VOID data]
To avoid redundancy, length field is not passed to request handler.
    '''
    print 'Client connected:',addr
    sock.settimeout(net.timeout)
    acc = '' #data accumulator
    try:
        while 1: 
            # receive and accumulate data
            chunk = sock.recv(BUFFSIZE)
            if not chunk:
                # check for 0-bytes-received disconnect
                print 'Client disconnected:',addr
                break
            acc += chunk

            # check if an entire packet has been accumulated
            if len(acc)>2:
                size = struct.unpack('!H',acc[:2])[0]
                # pop it off and handle it, if so
                if len(acc)>=size:
                    request,acc = acc[:size],acc[size:]
                    print 'Client',addr,'sent',size,'byte request.'
                    # make sure not to pass packet length into handler
                    response = request_handler(request[2:], addr)
                    response = struct.pack('!H',len(response)+2)+response
                    print 'Sending',len(response),'byte response to',addr
                    # ensure entire response is sent
                    sent = sock.send(response)
                    while sent<len(response):
                        response=response[sent:]
                        sent = sock.send(response)
                        # I forget, does 0 bytes sent mean conn closed too?
                        # Guess it's not -that- bad if it is. We can afford
                        # a few bugs.
    except socket.timeout:
        print 'Client timed out:',addr
    finally:
        #print 'Cleaning up client:',addr
        sock.shutdown(socket.SHUT_RDWR)
        sock.close()
        
def loop(request_handler):
    'Infinite TCP dispatcher server loop.'
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        server.bind((socket.gethostname(), net.port))
        server.listen(5)
        while 1:
            client,addr = server.accept()
            th = threading.Thread(target=connection_handler, 
                             args=(client,addr,request_handler))
            th.start()
    finally:
        #server.shutdown(socket.SHUT_RDWR) # needs to be closed one way, but 
        server.close()                      # which??

if __name__=='__main__':
    import protocol.node
    loop(protocol.node.handler)

